//
// Created on 2024/11/18.
//
// Node APIs are not fully supported. To solve the compilation error of the interface cannot be found,
// please include "napi/native_api.h".

#include <cstdint>
#include <sstream>
#include "cangjie_code_table.h"
#include "file_system.h"

constexpr uint64_t pow(uint64_t base, uint64_t p)
{
    return p == 0 ? 1 : base * pow(base, p - 1);
}

CangjieCodeTable::CangjieCodeTable(void)
{
    // cangjie code with max length of 5
    constexpr std::size_t maxCodeNum = pow(ALPHABET_MAX,CANGJIE_LENGTH_MAX);
    this->codeToChar_.resize(maxCodeNum);
    // set locale by system environment
    setlocale(LC_ALL, "");
}

std::string CangjieCodeTable::Deploy(std::string const &fileDir)
{
    std::string codeTablePath = fileDir + "/cangjie.dict.yaml";
    std::vector<std::string> lines;
    if (!ReadFile(this->codeTablePath_, lines)) {
        return "read " + this->codeTablePath_ + " failed";
    }
    if (!WriteFile(codeTablePath, lines)) {
        return "write " + codeTablePath + "failed";
    }
    return "deploy success to path " + codeTablePath;
}

std::size_t CangjieCodeTable::CodeToIndex(std::string code)
{
    // fill code with char 'a' up to length 5
    if (code.size() < CANGJIE_LENGTH_MAX) {
        code += std::string(CANGJIE_LENGTH_MAX - code.size(), 'a' - 1);
    }
    
    std::size_t index {};
    for (std::size_t i = 0; i < CANGJIE_LENGTH_MAX; ++i) {
        index *= ALPHABET_MAX;
        index += static_cast<std::size_t>(code[i] - 'a' + 1);
    }
    return index;
}

inline std::string Head(std::string const &code)
{
    if (code.size() <= 1) {
        return code;
    } else {
        return code.substr(0, 1);
    }
}

inline std::string Tail(std::string const &code)
{
    if (code.size() <= 1) {
        return code;
    } else {
        return code.substr(code.size() - 1);
    }
}

inline std::string HeadTail(std::string const &code)
{
    if (code.size() <= 2) {
        return code;
    } else {
        return code.substr(0, 1) + code.substr(code.size() - 1);
    }
}

inline std::string HeadHeadTail(std::string const &code)
{
    if (code.size() <= 3) {
        return code;
    } else {
        return code.substr(0, 2) + code.substr(code.size() - 1);
    }
}

inline std::string wcharToString(wchar_t wc)
{
    char buf[MB_CUR_MAX];
    std::size_t clen = wctomb(buf, wc);
    return std::string(buf, clen);
}

std::string CangjieCodeTable::WordToCode(WordToCodeMap &wordToCode, const std::string &word)
{
    // 计算保存 word 字符串需要的宽字符缓冲区长度
    std::size_t wlen = mbstowcs(nullptr, word.c_str(), 0);
    std::unique_ptr<wchar_t[]> wbuf = std::make_unique<wchar_t[]>(wlen + 1);
    mbstowcs(wbuf.get(), word.c_str(), wlen + 1);
    if (wlen == 2) {
        return HeadTail(wordToCode[wcharToString(wbuf[0])]) +
            HeadHeadTail(wordToCode[wcharToString(wbuf[1])]);
    } else if (wlen == 3) {
        return HeadTail(wordToCode[wcharToString(wbuf[0])]) +
            HeadTail(wordToCode[wcharToString(wbuf[1])]) +
            Tail(wordToCode[wcharToString(wbuf[2])]);
    } else if (wlen == 4) {
        return Head(wordToCode[wcharToString(wbuf[0])]) +
            Tail(wordToCode[wcharToString(wbuf[1])]) +
            HeadTail(wordToCode[wcharToString(wbuf[2])]) +
            Tail(wordToCode[wcharToString(wbuf[3])]);
    } else if (wlen >= 5) {
        return Head(wordToCode[wcharToString(wbuf[0])]) +
            Tail(wordToCode[wcharToString(wbuf[1])]) +
            Head(wordToCode[wcharToString(wbuf[2])]) +
            Tail(wordToCode[wcharToString(wbuf[wlen - 2])]) +
            Tail(wordToCode[wcharToString(wbuf[wlen - 1])]);
    } else {
        return "zzzzz";
    }
}

std::string CangjieCodeTable::LoadFromCodeTableBuffer(std::string const &buffer)
{
    std::vector<std::string> lines;
    std::string buf;
    std::stringstream ifs(buffer);
    while (std::getline(ifs, buf)) {
        lines.push_back(std::move(buf));
    }
    
    std::string ch;
    std::string code;
    bool skipLines = true;
    size_t count {};
    for (auto it = lines.cbegin(); it != lines.cend(); ++it) {
        if (it->size() >= 3 && it->substr(0, 3) == "...") {
            skipLines = false;
            continue;
        }
        if (skipLines) {
            continue;
        }
        if (it->size() >= 1 && it->substr(0, 1) == "#") {
            continue;
        }
        
        std::istringstream iss(*it);
        std::getline(iss, ch, '\t');
        std::getline(iss, code, '\t');
        std::size_t index = this->CodeToIndex(code);
        this->codeToChar_[index].chars.push_back(ch);
        this->wordToCode_[ch] = code;
        ++count;
    }
    return "load " + std::to_string(count) + " codes";
}

std::string CangjieCodeTable::LoadFromWordDictBuffer(std::string const &buffer)
{
    std::vector<std::string> lines;
    std::string buf;
    std::stringstream ifs(buffer);
    while (std::getline(ifs, buf)) {
        lines.push_back(std::move(buf));
    }
    
    std::string word;
    bool skipLines = true;
    size_t count {};
    for (auto it = lines.cbegin(); it != lines.cend(); ++it) {
        if (it->size() >= 3 && it->substr(0, 3) == "...") {
            skipLines = false;
            continue;
        }
        if (skipLines) {
            continue;
        }
        if (it->size() >= 1 && it->substr(0, 1) == "#") {
            continue;
        }
        
        std::istringstream iss(*it);
        std::getline(iss, word, '\t');
        std::string code = WordToCode(this->wordToCode_, word);
        std::size_t index = this->CodeToIndex(code);
        this->codeToChar_[index].chars.push_back(word);
        this->wordToCode_[word] = code;
        ++count;
    }
    return "load " + std::to_string(count) + " words";
}

std::string CangjieCodeTable::LoadCodeTable(std::string const &fileDir)
{
    std::string codeTablePath = fileDir + "/cangjie.dict.yaml";
    
    std::vector<std::string> lines;
    if (!ReadFile(codeTablePath, lines)) {
        return "read code table file failed: path " + codeTablePath;
    }
    
    std::string ch;
    std::string code;
    bool skipLines = true;
    std::size_t count {};
    for (auto it = lines.cbegin(); it != lines.cend(); ++it) {
        if (it->size() >= 3 && it->substr(0, 3) == "...") {
            skipLines = false;
            continue;
        }
        if (skipLines) {
            continue;
        }
        if (it->size() >= 1 && it->substr(0, 1) == "#") {
            continue;
        }
        
        std::istringstream iss(*it);
        std::getline(iss, ch, '\t');
        std::getline(iss, code, '\t');
        std::size_t index = this->CodeToIndex(code);
        this->codeToChar_[index].chars.push_back(ch);
        ++count;
    }
    auto index = this->CodeToIndex("b");
    return "index:" + std::to_string(index) + ", ch:[" + this->CodeToChar("b") + "]";
}

std::string CangjieCodeTable::Load(std::string const &fileDir)
{
    return this->LoadCodeTable(fileDir);
}

std::string CangjieCodeTable::CodeToChar(std::string const &code) const
{
    auto index = this->CodeToIndex(code);
    auto group = this->codeToChar_[index];
    if (group.chars.empty()) {
        return code;
    }
    return group.chars[0];
}

std::vector<std::string> CangjieCodeTable::GetCandidates(std::string const &code) const
{
    if (code.size() > 5) {
        return {};
    }
    
    std::size_t start = 0;
    for (std::size_t i = 0; i < code.size(); ++i) {
        start += (code[i] - 'a' + 1) * pow(ALPHABET_MAX, CANGJIE_LENGTH_MAX - i - 1);
    }
    std::size_t nCode = pow(ALPHABET_MAX, CANGJIE_LENGTH_MAX - code.size());
    
    std::vector<std::string> candidates;
    std::size_t count = 0;
    for (std::size_t i = 0; i < nCode; ++i) {
        auto const &chars = this->codeToChar_[start + i];
        for (std::size_t j = 0; j < chars.chars.size(); ++j) {
            candidates.push_back(chars.chars[j]);
            ++count;
            if (count >= 50) {
                return candidates;
            }
        }
    }
    return candidates;
}